通过计组课作为一个引子,自己去学习一下浮点数规则还是挺有意思的。
IEEE 754浮点数——重要点备忘
通过计组课作为一个引子,自己去学习一下浮点数规则还是挺有意思的。
结构定义
以32位浮点数为例:
sign
:1比特exponent
:8比特significand
:23比特
重要知识点
特殊值
0,+inf
,-inf
,NaN
具体定义不细说。
subnormal number
除去特殊值,当exponent
全部为0时,则会变成subnormal number
,是用于尽可能描述接近于0的极小数的。其尾部隐藏的整数时0而不是一般数的1。
同时subnormal
数的指数偏移为126而不是一般数的127,所以指数位全零,代表的意思也还是-126次方,和一般数中指数位为0b00000001
的意思一样。(但是隐藏整数不一样!)
举个例子。z3
里面提供了相关转换机制,所以用于做示范。
1 | # from z3 import * |
其中,b
是subnormal
数,因为其exp
是0。可以发现它是一个离0很近的数字,但也能很轻松地表示出来。
a
是一般数,它的exp
为1,代表的实际指数和b
的一模一样,都是-126
,且significand
都代表了0.5
,但是由于隐藏数的不同,subnormal
为0,而一般数为1,所以一个是0.5
,一个是1.5
。
加法
将指数小的数字向指数大的转换。其中小数部位必然可能出现精度损失现象。
比如1.0101*2**1
+1.1*2**4
,其中1.0101*2**1
要转换成0.0010101*2**4
。
然后小数上进行加法。
乘法
小数位相乘,指数位相加。然后再稍微调整一下,塞到浮点数结构里面去。
注意
关于计组课上的“浮点数”
根据“简化模型”的理念,计组课上的浮点数模型并没有严格按照IEEE标准去执行,尤其是没有隐藏的整数1,而默认全是0,也就是IEEE754的subnormal number
。
SIMD/SSE/AVX浮点指令集
遇到浮点数了,肯定得复习一下汇编中关于FPU
的操作。
FPU栈
在不开启优化的条件下,即使是一般Windows程序也会使用FPU栈来进行浮点数操作,而不是使用xmm
向量寄存器。
汇编语言FPU寄存器栈(register stack) (biancheng.net)
关于数据结构和一般入栈出栈指令
汇编语言浮点数异常与常用指令集 (biancheng.net)
FINIT
FLD
FST
关于计算指令
总结一下:
FXX
就是一般的XX指令。会保留栈不变。FXXP
就是一般XX指令后还会弹出栈顶元素,仅保留1个FIXX
就是加载操作数上的整数数据,先转换成双精度浮点数,然后再和FPU栈上的浮点数进行XX运算。FDIV
是ST(1)
除以ST(0)
。
浮点数的比较
汇编语言FCOM指令:比较浮点数值 (biancheng.net)
FCOM
,FCOMP
,FCOMPP
FNSTSW AX
,SAHF
传递标志寄存器。
其他向量指令集
SSE/AVX啥的是真的鸡儿多。一个项目可能就有500多个指令集,学个屁。
不过我觉得可以收集一下资料,方便以后遇到了进行检索。
中文部分SSE2 128位指令集
SSE指令集 - DeeLMind - 博客园 (cnblogs.com)
绝对不完整的。不过能凑合看
SSE+AVX中文
SSE5 128bit
http://developer.amd.com/wordpress/media/2012/10/AMD64_128_Bit_SSE5_Instrs.pdf
纯粹的官方技术文档。慎入
AVX512
SSE, AVX 128, 256
https://www.amd.com/system/files/TechDocs/43479.pdf
Wiki
https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
其他
SSE Z3(未实现)
准备考虑一下在z3
或者claripy
上面实现若干SSE指令函数。这样遇到因为超级优化而导致一堆SSE指令的程序时,就不会再需要麻麻烦烦的一个指令一个指令慢慢去实现了。
优化
一篇手动使用SSE指令集进行程序优化的骚操作。
Intel SSE指令集 - 简书 (jianshu.com)
不过一般情况下,只要编程习惯好一点,一般编译器会帮你进行优化的。
一般求和函数
1 | int normal_add(int *a, size_t n) { |
1 | ./SSETest1 100000000 |
分成四路
1 | int normal_add_loop4(int *a, size_t n) { |
1 | ./SSETest1 100000000 |
虽然并不是并行计算或者向量化,但是由于for
循环遍历次数少了,降低了jmp
跳转的次数和时间浪费,所以有很大的优化效果。
反编译结果:
1 | __int64 __fastcall normal_add_loop4(int *a1, unsigned __int64 a2) |
汇编中也只有一般的mov
和add
。
SSE版本
1 | int sse_add(int *a, size_t n) { |
1 | g++ -O0 -msse4 SSETest1.cpp -o SSETest1 |
1 | ./SSETest1 100000000 |
进行了进一步优化。
#include <nmmintrin.h>
后使用SSE函数
参考
懒得再重新造轮子了。直接贴一下我研究过的文章。
https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#
这玩意才是指令集完善版本!
https://stackoverflow.com/questions/11228855/header-files-for-x86-simd-intrinsics
关于各个导入头文件的意义
1 | <mmintrin.h> MMX |
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/03/09/Float-Point Number/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!